﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;
using System.Web.Mvc;

namespace Toolkit.Web.Mvc.TempDataProviders
{
    /// <summary>
    /// Cookie TempData Provider.
    /// PLEASE NOTE: Cookie size limitations restrict the size of cookies to less than appoximately 4095 bytes. You must
    /// take into account the size of the data you are inserting into the TempData collection when utilizing this provider.
    /// 
    /// To override the MVC default TempData provider (SessionState), put the following into your controller constructor:
    /// 
    /// TempDataProvider = new CookieTempDataProvider("{COOKIE_NAME}");
    /// </summary>
    public class CookieTempDataProvider : ITempDataProvider
    {
        public CookieTempDataProvider(string cookieName)
        {
            if (string.IsNullOrEmpty(cookieName))
            {
                throw new ArgumentException();
            }

            CookieName = cookieName;
        }

        public string CookieName
        {
            get;
            private set;
        }

        public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
        {
            IDictionary<string, object> tempDataDictionary = null;
            HttpCookie cookie = controllerContext.HttpContext.Request.Cookies[CookieName];

            if (cookie != null && !string.IsNullOrEmpty(cookie.Value))
            {
                tempDataDictionary = Deserialize(cookie.Value);

                if (tempDataDictionary != null)
                {
                    // If we got it from the cookie collection, remove it so that no other request gets it.

                    // TODO: make sure this code is not reached on redirect requests.

                    cookie.Expires = DateTime.MinValue;
                    cookie.Value = string.Empty;

                    if (controllerContext.HttpContext.Response != null && controllerContext.HttpContext.Response.Cookies != null)
                    {
                        HttpCookie responseCookie = controllerContext.HttpContext.Response.Cookies[CookieName];

                        if (responseCookie != null)
                        {
                            cookie.Expires = DateTime.MinValue;
                            cookie.Value = string.Empty;
                        }
                    }
                }
            }

            return tempDataDictionary;
        }

        public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
        {
            string cookieValue = Serialize(values);

            HttpCookie cookie = new HttpCookie(CookieName);
            cookie.Value = cookieValue;
            cookie.Secure = controllerContext.HttpContext.Request.IsSecureConnection;

            if (controllerContext.HttpContext.Response.Cookies[CookieName] != null)
            {
                controllerContext.HttpContext.Response.Cookies.Set(cookie);
            }
            else
            {
                controllerContext.HttpContext.Response.Cookies.Add(cookie);
            }
        }

        private IDictionary<string, object> Deserialize(string value)
        {
            if (string.IsNullOrEmpty(value)) return new Dictionary<string, object>();
            try
            {
                byte[] bytes = Convert.FromBase64String(value);
                MemoryStream memoryStream = new MemoryStream(bytes);
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                return binaryFormatter.Deserialize(memoryStream, null) as IDictionary<string, object>;
            }
            catch (Exception)
            {
                return null;
            }
        }

        private string Serialize(IDictionary<string, object> values)
        {
            if (values == null || values.Count == 0) return string.Empty;
            MemoryStream memoryStream = new MemoryStream();
            memoryStream.Seek(0, SeekOrigin.Begin);
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, values);
            memoryStream.Seek(0, SeekOrigin.Begin);
            byte[] bytes = memoryStream.ToArray();
            return Convert.ToBase64String(bytes);
        }
    }
}
